28 Three.js 光照系统配置与阴影渲染
Three.js 光照系统配置与阴影渲染
关联:索引
要解决的问题
- 工业 3D 场景里,“看得清”为什么不仅是相机问题,更是光照问题?没有光会发生什么?
- 环境光、方向光、点光分别解决什么需求?为什么工业场景最常用的是“环境光 + 方向光”组合?
- 我调了灯但场景还是“灰、平、没层次”,问题可能出在强度/颜色/位置,还是材质/颜色空间?
- 阴影为什么经常“开了没效果”?Three.js 阴影渲染必须同时满足哪些条件?
- 阴影边缘锯齿/抖动/漏光怎么处理?哪些参数是“画质—性能”必须权衡的关键点?
本讲定位(与上一讲衔接,避免重复)
- 已具备:Three.js 最小渲染闭环(Scene/Camera/Renderer/Mesh)、几何体与基础材质创建、工业基础场景雏形(工作台/立柱/工件等)。
- 本讲新增:工业场景光照需求分析;AmbientLight / DirectionalLight / PointLight 的作用与配置;强度/颜色/位置调试方法;阴影渲染的核心条件与排错;阴影画质与性能的工程取舍。
- 本讲不展开:PBR 复杂材质体系(Standard/Physical 深入)、HDR 环境贴图、后处理(Bloom/SSAO)、多光源复杂布光方案(后续可扩展)。
章节内容(本讲核心)
- 工业 3D 场景光照需求分析:可读性、层次感、空间参照、重点对象突出
- Three.js 光照类型与工业适配:
- 环境光 AmbientLight:整体补光、降低对比
- 方向光 DirectionalLight:模拟日光/顶灯方向性,塑造明暗与阴影
- 点光 PointLight:局部补光/指示灯/局部工位照明
- 光照调参方法:强度 intensity、颜色 color、位置 position/target 的调试路径
- 阴影渲染核心配置:渲染器开启阴影、光源开启阴影、物体投射/接收阴影
- 光照与阴影对工业场景真实感的影响:层次、空间深度、尺度感与安全感知
环境与先修(默认沿用上一讲工程)
先修要求:
- 已有 Vue3 + Vite + TypeScript 工程,并已安装 Three.js。
- 已能在页面中渲染出一个工业基础场景(至少包含地面/工作台/工件之一)。
如你需要补装依赖(仅在未安装时执行):
# 安装 Three.js 运行时依赖
npm i three
# 仅在 TypeScript 报 “找不到声明文件” 时再安装(老工程更常见)
npm i -D @types/three
解释:
three:Three.js 核心库。@types/three:TypeScript 类型定义(通常新版本 three 已内置类型;仅在你的工程提示缺少声明文件时再安装)。
在 Three.js 里,一个最容易踩的坑是:你换成“受光材质”(Lambert/Phong/Standard),但没有任何光源,结果模型几乎全黑。
你要记住的工程结论:
- 光照不是装饰,而是“信息呈现手段”:
- 可读性:让结构边界、转角、孔洞、凸起能被看清
- 层次感:让前后遮挡与空间深度更明显
- 重点突出:让关键设备/危险区域“更亮更显眼”
- 工业场景经常追求“稳定、均匀、可解释”,而不是舞台灯光式强烈戏剧化。
只复习一句话,避免重复上一讲:
不是所有材质都受光照影响:MeshBasicMaterial 不吃灯光;Lambert/Phong/Standard 会随灯光变化。
快速自检方法:
- 你先用
MeshBasicMaterial验证“物体确实在那儿、颜色正确”。 - 再切到
MeshLambertMaterial / MeshPhongMaterial验证“光照确实生效”。
1) 环境光 AmbientLight:整体补光、降低对比
适用场景(工业常用):
- 让阴影区域不至于死黑,保证结构可读性
- 作为“基础底光”,配合方向光塑造层次
import * as THREE from 'three';
// 环境光:提供全局补光(无方向、无阴影)
const ambient = new THREE.AmbientLight(0xffffff, 0.35);
// 将光源加入场景,才会生效
scene.add(ambient);
解释:
- 第一个参数
0xffffff:光颜色(白光最通用)。 - 第二个参数
0.35:强度(建议从 0.2~0.6 区间试起)。 - AmbientLight 没有方向,不会产生阴影。
2) 方向光 DirectionalLight:方向性主光,塑造明暗与阴影
适用场景(工业最常用):
- 模拟室内顶灯/天窗/日光方向
- 用“方向”制造阴影,形成空间层次与尺度感
import * as THREE from 'three';
// 方向光:模拟高位主光(可产生阴影)
const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
// position 决定光照方向(从该点“照向” target)
dirLight.position.set(5, 8, 3);
// target 指定方向光照向的目标点
dirLight.target.position.set(0, 0, 0);
scene.add(dirLight);
scene.add(dirLight.target);
解释:
position决定光从哪里来(相当于光的“方向”)。target决定光照向哪里;不把target加入场景时,调整可能不生效或难以理解。
3) 点光 PointLight:局部补光、工位灯、指示灯
适用场景(工业常见):
- 某个工位局部照明(让该区域更亮)
- 设备状态指示灯(更偏“氛围 + 提示”,不是主照明)
import * as THREE from 'three';
// 点光:局部补光(颜色、强度、影响距离、衰减)
const point = new THREE.PointLight(0xffddaa, 0.8, 10, 2);
// 放在需要照亮的工位附近
point.position.set(1.2, 1.6, 0.5);
scene.add(point);
解释:
color:暖色点光常用于模拟工位灯。intensity:强度。distance:影响范围(超过距离后衰减到接近 0)。decay:衰减指数,越大越“近亮远暗”,更像真实点光。
你调光不要盲调,按下面顺序走,最快收敛:
- 先让“能看清结构”:从 AmbientLight 开始
- 目标:阴影区域也能看清轮廓(但不要照得像“无阴影的平面图”)。
- 建议:先把环境光强度定在 0.25~0.45。
- 再加“主光制造层次”:DirectionalLight 的方向优先于强度
- 目标:物体有明显受光面与背光面;地面/工作台能出现明确方向的明暗。
- 调试方法:先移动
dirLight.position(方向),再调intensity(明暗)。 - 常见经验:
position.y高一些(例如 6~12),模拟顶灯/高位灯。
- 最后用“点光做局部强调”:PointLight 不要抢主光
- 目标:某局部更亮,但不让全场景色调失真。
- 常见坑:点光强度太大导致“局部过曝、颜色偏黄”。
- 颜色的工程原则:白光为主,暖/冷做辅
- 工业视觉通常需要“中性、可解释”,白光主照明更通用。
- 暖光/冷光用于区分区域或提示状态,但要控制强度,避免“舞台感”。
没有阴影时,工业场景最常见的观感问题:
- 物体像“漂浮”在地面上,接触关系不可信
- 尺度感弱:大设备与小部件的空间关系不直观
- 空间深度弱:画面更像“贴图”而不是三维空间
工程结论:
- 阴影不是“越多越好”,而是要“该有的接触阴影必须有”,否则工业场景可信度会明显下降。
你要把这三条当作“阴影能不能出”的门禁条件:
- 渲染器开启阴影
// 全局开启阴影(不开这个,后面所有 cast/receive 都不会生效) renderer.shadowMap.enabled = true; // 柔化阴影边缘(更自然,但更吃性能) renderer.shadowMap.type = THREE.PCFSoftShadowMap;
解释:
enabled = true:全局开启阴影渲染。PCFSoftShadowMap:更柔和,工业场景通常比硬边阴影更自然(代价是更吃性能)。
- 光源开启阴影(并且该光源支持阴影)
// 允许该光源投射阴影(AmbientLight 不支持阴影) dirLight.castShadow = true;
解释:
- AmbientLight 不产生阴影;DirectionalLight / PointLight 才可能产生阴影(本讲主用 DirectionalLight)。
- 物体设置投射/接收阴影
// 地面/台面通常接收阴影 floor.receiveShadow = true; // 关键物体投射阴影 part.castShadow = true; table.castShadow = true; table.receiveShadow = true;
解释:
castShadow:该物体会把阴影投到别的物体上。receiveShadow:该物体会接收别的物体投过来的阴影。- 地面/台面通常
receiveShadow = true;关键设备通常castShadow = true。 - 接收阴影的物体材质需要“受光”(例如 Lambert/Phong/Standard)。如果你用的是
MeshBasicMaterial,即使设置了receiveShadow = true,阴影也可能看不出来。
本工坊目标(与“学生任务”一致):
- 让“工作台/工件”能在地面和台面上投下可接受质量的阴影
- 支持对比:无光照 / 有光照 / 有阴影(至少能切换“阴影开关”并观察差异)
- 开启渲染器阴影 + 提供开关
import * as THREE from 'three'; import { ref, watch } from 'vue'; // UI 开关:用于对比“开启/关闭阴影”的差异 const shadowsEnabled = ref(true); // onMounted 内:创建 renderer 后 // shadowMap.enabled 必须为 true 才会渲染阴影 renderer.shadowMap.enabled = shadowsEnabled.value; renderer.shadowMap.type = THREE.PCFSoftShadowMap; watch([shadowsEnabled], () => { if (!renderer) return; renderer.shadowMap.enabled = shadowsEnabled.value; });
解释:
shadowsEnabled用于对比“开/关阴影”的视觉差异。shadowMap.enabled在运行中可以切换,但切换后你要观察性能变化与阴影一致性。
- 方向光开启阴影,并配置阴影贴图分辨率与相机范围
// onMounted 内:创建 dirLight 后 // 允许方向光投射阴影 dirLight.castShadow = true; // 阴影贴图分辨率:越大越清晰,但更吃 GPU dirLight.shadow.mapSize.set(1024, 1024); // 阴影相机:决定“哪些区域会生成阴影” // 范围太大 -> 阴影更糊;范围太小 -> 阴影被裁切 dirLight.shadow.camera.near = 0.5; dirLight.shadow.camera.far = 30; dirLight.shadow.camera.left = -6; dirLight.shadow.camera.right = 6; dirLight.shadow.camera.top = 6; dirLight.shadow.camera.bottom = -6; // 阴影瑕疵常用修正:bias / normalBias // 通常需要结合场景尺度微调 dirLight.shadow.bias = -0.0002; dirLight.shadow.normalBias = 0.02; // 阴影柔化半径(不是所有类型都明显生效,取决于 shadowMap.type) dirLight.shadow.radius = 2;
解释:
mapSize:从 512 → 1024 → 2048 逐级尝试,找到“能用且不卡”的平衡点。shadow.camera:范围太小会“阴影被裁掉”,范围太大阴影会变糊(同样分辨率覆盖更大区域)。bias/normalBias:用于解决“阴影痤疮(acne)”或“悬浮(peter-panning)”,两者常常需要反复微调。
- 物体设置投射/接收阴影(落到你场景里的具体 Mesh 上)
推荐改法:让 buildIndustrialBase 返回对象引用(示例):
type IndustrialBase = {
floor: THREE.Mesh;
table: THREE.Mesh;
part: THREE.Mesh;
};
function buildIndustrialBase(scene: THREE.Scene): IndustrialBase {
// 通过返回引用,便于第二课时在外部统一开启 cast/receiveShadow
const floorGeo = new THREE.PlaneGeometry(10, 10);
const floorMat = new THREE.MeshLambertMaterial({ color: 0x2b2f36 });
const floor = new THREE.Mesh(floorGeo, floorMat);
floor.rotation.x = -Math.PI / 2;
scene.add(floor);
const tableGeo = new THREE.BoxGeometry(3, 0.3, 1.6);
const tableMat = new THREE.MeshLambertMaterial({ color: 0x4b5563 });
const table = new THREE.Mesh(tableGeo, tableMat);
table.position.set(0, 0.15, 0);
scene.add(table);
const partGeo = new THREE.CylinderGeometry(0.18, 0.18, 0.5, 24);
const partMat = new THREE.MeshPhongMaterial({ color: 0x93c5fd, shininess: 80 });
const part = new THREE.Mesh(partGeo, partMat);
part.position.set(0.6, 0.55, 0.2);
scene.add(part);
return { floor, table, part };
}
然后在 onMounted 里拿到引用再设置阴影:
const base = buildIndustrialBase(scene);
// 接收阴影(地面/台面)
base.floor.receiveShadow = true;
// 投射阴影(关键物体)
base.table.castShadow = true;
base.table.receiveShadow = true;
base.part.castShadow = true;
解释:
- 阴影是否“可信”很大程度来自接触面:地面/台面一定要接收阴影。
- 不是所有物体都必须投射阴影:小零件投影过多会增加开销、造成画面噪点。
-
加一个“阴影调优面板”(最小可用,不引入额外库)
在模板面板里加两项(示例):<label> 开启阴影 <input v-model="shadowsEnabled" type="checkbox" /> </label> <label> 阴影分辨率(mapSize) <select v-model.number="shadowMapSize"> <option :value="512">512</option> <option :value="1024">1024</option> <option :value="2048">2048</option> </select> </label>
对应脚本增加两个状态并同步到方向光:
import { ref, watch } from 'vue';
// UI 选择:阴影贴图分辨率(越大越清晰,但更吃资源)
const shadowMapSize = ref(1024);
watch([shadowMapSize], () => {
if (!dirLight) return;
// 运行时调整 shadow map 分辨率(用于对比画质与性能)
dirLight.shadow.mapSize.set(shadowMapSize.value, shadowMapSize.value);
});
解释:
mapSize是最直观的画质参数之一:同样场景下,512 明显锯齿,1024 通常可用,2048 更清晰但更吃资源。- 工程备注:在部分 Three.js/浏览器组合中,运行时切换
mapSize可能需要“重建 shadow map(释放旧贴图并置空)”才能立即生效;本次配套项目已实现该处理逻辑。
四、阴影渲染失败的常见问题(速查表)
按“最短路径”排查(建议照顺序来):
- 阴影完全没有:
- 是否
renderer.shadowMap.enabled = true - 是否
dirLight.castShadow = true - 阴影接收物是否
receiveShadow = true,投射物是否castShadow = true - 你是否用的是 AmbientLight(它不产生阴影)
- 阴影接收物是否使用“受光材质”(Lambert/Phong/Standard);如果是
MeshBasicMaterial,阴影可能看不出来
- 阴影有但“很糊/很淡”:
dirLight.shadow.mapSize是否太小shadow.camera范围是否太大(同样分辨率覆盖更大区域会更糊)- 灯光强度是否过弱(阴影对比度低)
- 阴影出现“锯齿/抖动/闪烁”:
- 提高
mapSize(512→1024) - 使用
PCFSoftShadowMap - 调整相机远近裁剪与方向光位置(避免超大视域)
- 阴影“漏光/漂浮/一片脏”:
-
漏光/漂浮(peter-panning):尝试减小
normalBias或调整bias(接近 0) -
一片脏(shadow acne):尝试增大
normalBias或把bias调成略微负值 -
光照可读性:背光侧仍能看清结构轮廓(不是死黑)
-
光照层次:受光面与背光面有明显差异,且方向一致
-
重点突出:工件或关键设备明显更吸引视线(可通过局部点光实现)
-
阴影接触:工件在台面/地面有接触阴影,不漂浮
-
阴影稳定:相机轻微移动或 resize 不出现明显闪烁
-
性能可接受:阴影分辨率和光源数量不导致明显掉帧(至少主观不卡)
为之前搭建的工业基础场景添加光照系统,配置环境光 + 方向光组合,开启阴影渲染,调试光照参数,实现真实感基础场景。
- 环境光 + 方向光同时存在,且能通过面板调强度
- 阴影开关可切换,对比差异明显
- 至少一个物体
castShadow,至少一个平面receiveShadow - 阴影质量经过一次调优:
mapSize与bias/normalBias有合理取值
- 配置环境光与方向光,调整光照强度、颜色:
- 提交:组件代码(或截图 + 关键参数记录)
- 开启渲染器、光源、几何体的阴影相关配置:
- 提交:关键代码片段 + 说明“三条件”分别在哪里实现
- 调试阴影模糊度、分辨率,优化阴影渲染效果:
- 提交:至少两档 mapSize 的对比截图(512 vs 1024 或 1024 vs 2048)
- 对比无光照、有光照、有阴影的场景差异:
- 提交:三张对比截图或录屏
大模型任务(可直接复制的 AI 指令模板)
任务 1:生成工业 3D 场景最优光照配置代码
把下面提示词复制给 AI(你可以替换场景描述):
你是 Three.js 工业可视化工程师。我有一个工业基础场景:地面 Plane、工作台 Box、一个工件 Cylinder,使用 Vue3 + TS + Three.js。请给出“环境光 + 方向光”为主的光照配置代码,并开启阴影渲染(renderer、光源、物体 cast/receive),同时给出阴影画质调优参数(mapSize、shadow camera、bias/normalBias、shadow type)。代码要可直接粘贴进 Vue
<script setup lang="ts">,不引入额外库。最后附上自检清单:阴影没出现时如何排查。
期望输出校验点:
- 是否包含三条件(renderer / light / object)
- 是否给出 shadow camera 的范围与 mapSize
- 是否包含 bias/normalBias 的解释与建议区间
任务 2:根据场景需求推荐光照类型组合
提示词:
给我 3 套工业场景的光照组合方案:①室内均匀照明(工厂车间)②有明显方向光(天窗/侧窗)③局部工位重点照明。每套写出需要的光源类型(Ambient/Directional/Point),给出建议强度范围、颜色倾向(中性/偏暖/偏冷),并说明优缺点与适用条件。
期望输出校验点:
- 每套都有“主光 + 补光”的逻辑
- 能解释“为什么不用纯环境光”或“为什么点光不做主光”
任务 3:排查阴影渲染失败的常见问题及解决方案
提示词:
我在 Three.js 里开了阴影但看不到影子。请按最短排查路径列出 10 个常见原因与对应修复方法,要求覆盖:renderer.shadowMap、light.castShadow、object.castShadow/receiveShadow、shadow.camera 范围、mapSize、bias/normalBias、材质是否受光、光源类型是否支持阴影、相机裁剪、性能/显卡限制。输出要像“排障手册”一样可直接照做。
期望输出校验点:
- 每条原因都包含“怎么验证”与“怎么修”
- 有明确优先级(先查哪三条)
作业
Markdown 与代码自检(发布前已检查)
- 标题层级连续(从
#到##/###逐级推进),无跳级。 - 所有代码块均闭合,语言标签已标注(
ts/vue/bash)。 - 示例代码不依赖额外第三方库(未引入 GUI/控制器库),可直接放入 Vue3 + TS 工程。